﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Silvermoon.OpenGL.Native;
using Silvermoon.OpenGL;
using System.Reflection;
using System.IO;
using Silvermoon.Utils;
using Silvermoon.Core;
using System.Runtime.InteropServices;

namespace Silvermoon.Shapes
{
    /// <summary>
    /// Contains core functionality to render most kinds of texture shapes.
    /// texture mapping is defined by StyleCore. You can use one texture or two textures to perform multitexturing.
    /// </summary>
    /// <remarks>
    /// the arrays created have the follwowing graph:
    /// 
    /// 01  45
    /// 23  67
    /// 
    /// 89 CD
    /// AB EF
    /// 
    /// with 4 colors and
    /// 
    /// 00 01    04 05
    /// 02 03    06 07
    /// 
    /// 08 09    0C 0D
    /// 0A 0B   0E 0F
    /// 
    /// 10 11    14 15
    /// 12 13    16 17
    /// 
    /// 18 19    1C 1D
    /// 1A 1B   1E 1F
    /// 
    /// with 5 colors
    /// 
    /// where 0...G represent the order of each point in the index.
    /// </remarks>    
    public abstract class BackgroundShapeBase : Shape
    {
        #region constants

        const int ColorIndex = 1;
        const int VertexIndex = 0;

        const float one = 1f;

        static readonly float[] textureArraySimple = new float[]
        {
            one, 0, one, -one, 0, 0, 0, -one 
        };

        static readonly float[] textureArrayPlastic = new float[]
            {
                0f,-1f,
                0.5f,-1f,
                0f,-0.5f,
                0.5f,-0.5f,

                0.5f,-1f,
                1f,-1f,
                0.5f,-0.5f,
                1f,-0.5f,

                0f,-0.5f,
                0.5f,-0.5f,
                0f,0f,
                0.5f,0f,

                0.5f,-0.5f,
                1f,-0.5f,
                0.5f,0f,
                1f,0f
            };

        static readonly float[] textureArrayChrome = new float[]
            {
                0f,-1f,
                .5f,-1f,
                .5f,-1f,
                1f,-1f,

                0f,-.5f,
                .5f,-.5f,
                .5f,-.5f,
                1f,-.5f,

                0f,-.5f,
                .5f,-.5f,
                .5f,-.5f,
                1f,-.5f,

                 0f,-.5f,
                .5f,-.5f,
                .5f,-.5f,
                1f,-.5f,

                0f,-.5f,
                .5f,-.5f,
                .5f,-.5f,
                1f,-.5f,

                0f,0f,
                .5f,0f,
                .5f,0f,
                1f,0f
            };


        static readonly short[] simpleIndexes = new short[]
        {
            0,1,2,3,2,1
        };

        static readonly short[] plasticIndexes = new short[]
        {
            3,1,0, 3,2,0,  // top left
            7,5,4, 7,6,4, // top right
            11,9,8, 11,10,8,       // bottom left
            15,13,12, 15,14,12, // bottom right
            6,4,1, 6,3,1,           //top center
            14,12,9,14,11,9,     // bottom center
            9,3,2,9,8,2,            //center left,
            13,7,6,13,12,6,      // center right    
            12,6,3,12,9,3          // center center
        };

        static readonly short[] chromeIndexes = new short[]
        {
            5,1,0,5,4,0,
            9,5,4,9,8,4,
            17,13,12,17,16,12,
            21,17,16,21,20,16,

            6,2,1,6,5,1,
            10,6,5,10,9,5,
            18,14,13,18,17,13,
            22,18,17,22,21,17,

            7,3,2,7,6,2,
            11,7,6,11,10,6,
            19,15,14,19,18,14,
            23,19,18,23,22,18
        };


        #endregion
        #region fields

        protected uint[] glesBuffers;
        protected int[] colorArray;
        protected short[] vertexArray;
        private int alpha;

        #endregion
        #region ctor

        public BackgroundShapeBase()
            : base()
        {
            EnableGlesBuffer = true;
        }


        public bool isDisposed;

        public override void Dispose()
        {
            isDisposed = true;
            DisposeBuffers();
            base.Dispose();
        }



        #endregion
        #region props

        private static uint[] staticBuffers;

        private static void EnsureStaticBuffers()
        {
            if (staticBuffers == null)
            {
                staticBuffers = gl.GenBuffers(5);
                gl.BindBuffer(BufferTarget.ArrayBuffer, staticBuffers[0]);
                gl.BufferData(BufferTarget.ArrayBuffer, sizeof(float) * textureArrayPlastic.Length, textureArrayPlastic, BufferUsage.StaticDraw);

                gl.BindBuffer(BufferTarget.ArrayBuffer, staticBuffers[1]);
                gl.BufferData(BufferTarget.ArrayBuffer, sizeof(float) * textureArrayChrome.Length, textureArrayChrome, BufferUsage.StaticDraw);

                gl.BindBuffer(BufferTarget.ElementArrayBuffer, staticBuffers[2]);
                gl.BufferData(BufferTarget.ElementArrayBuffer, sizeof(short) * plasticIndexes.Length, plasticIndexes, BufferUsage.StaticDraw);

                gl.BindBuffer(BufferTarget.ElementArrayBuffer, staticBuffers[3]);
                gl.BufferData(BufferTarget.ElementArrayBuffer, sizeof(short) * chromeIndexes.Length, chromeIndexes, BufferUsage.StaticDraw);

                //gl.BindBuffer(BufferTarget.ElementArrayBuffer, staticBuffers[4]);
                //gl.BufferData(BufferTarget.ElementArrayBuffer, sizeof(short) * simpleIndexes.Length, simpleIndexes, BufferUsage.StaticDraw);

                gl.BindBuffer(BufferTarget.ArrayBuffer, staticBuffers[4]);
                gl.BufferData(BufferTarget.ArrayBuffer, sizeof(float) * textureArraySimple.Length, textureArraySimple, BufferUsage.StaticDraw);

                gl.BindBuffer(BufferTarget.ArrayBuffer, 0);
                gl.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
            }
        }

        /// <summary>
        /// Gets or sets whether GLES buffering for vertex, color and index pointers is enabled (default=true).
        /// </summary>
        public bool EnableGlesBuffer { get; set; }


        /// <summary>
        /// If overriden, gets the style how to render a texture. See also <seealso cref="T:RenderStyle"/>.
        /// </summary>
        protected virtual RenderStyle StyleCore
        {
            get { return RenderStyle.Edged; }
        }

        /// <summary>
        /// If overriden, gets the texture to render with the specified style.
        /// </summary>
        protected virtual Texture TextureCore
        {
            get { return null; }
        }

        /// <summary>
        /// If overriden, gets the texture that supposed to contain the alpha channel. In this case multitexturing is used.
        /// </summary>
        /// <example>
        /// You can specify a AlphaTexture that contains only the alpha values (TextureFormat.Alpha) together with a texture that contains the luminance (TextureFormat.Lumnance).
        /// If the images are the same, this produces an RGBA_88888 texture, since TextureFormat.LuminanceAlpha has only RGBA_4444.
        /// But note that multitexturing has a performance penalty of 80%!
        /// </example>
        protected virtual Texture AlphaTextureCore
        {
            get { return null; }
        }

        /// <summary>
        /// If overriden, gets the color that is used to blend with the texture(s).
        /// </summary>
        protected virtual Color ColorCore
        {
            get { return Color.White; }
        }


        #endregion
        #region methods
        protected void DisposeBuffers()
        {
            gl.DeleteBuffers(glesBuffers);
            glesBuffers = null;
           // this.IsInvalidated = true;
        }

        public virtual void Initialize()
        {
            EnsureStaticBuffers();
            //if (Texture == null) throw new ArgumentNullException("Texture", "Value not set.");
            CreateColorArray(Opacity);
            CreateVertexArray();
            CreateBuffers();
        }

        protected virtual void CreateVertexArray()
        {
            switch (StyleCore)
            {
                case RenderStyle.Simple:
                    vertexArray = VertexUtil.SquaredArray(Size);
                    break;

                case RenderStyle.FlatChrome:
                case RenderStyle.Metalic:
                    vertexArray = VertexUtil.Create6QuadArray(Size, TextureCore);
                    break;

                default:
                    vertexArray = VertexUtil.Create4QuadArray(Size, TextureCore);
                    break;
            }
        }

        protected void CreateColorArray(int alpha)
        {
            this.alpha = alpha;
            switch (StyleCore)
            {
                case RenderStyle.Simple:
                case RenderStyle.Edged:
                    colorArray = null;
                    break;

                //                case RenderStyle.FlatChrome:
                case RenderStyle.Gradiented:
                case RenderStyle.Plastic:
                    colorArray = ColorArrays.Create4QuadArray(ColorCore, alpha, StyleCore);
                    break;

                case RenderStyle.FlatChrome:
                case RenderStyle.Metalic:
                    colorArray = ColorArrays.Create6QuadArray(ColorCore, alpha, TextureCore, Height);
                    break;

                default:
                    throw new NotImplementedException();
            }
        }

        protected override void Layout()
        {
            Initialize();
            base.Layout();
        }

        protected void CreateBuffers()
        {
            if (EnableGlesBuffer)
            {
                if (glesBuffers == null) glesBuffers = gl.GenBuffers(1);

                if (glesBuffers[0] == 0) throw new OpenGlException("glGenBuffers");
                //  CreateColorArrayBuffer();
                if (vertexArray != null)
                {
                    gl.BindBuffer(BufferTarget.ArrayBuffer, glesBuffers[VertexIndex]);
                    gl.BufferData(BufferTarget.ArrayBuffer, sizeof(short) * vertexArray.Length, vertexArray, BufferUsage.StaticDraw);
                    gl.CheckError();
                    vertexArray = null;
                }

                gl.BindBuffer(BufferTarget.ArrayBuffer, 0);
            }
        }

        /// <summary>
        /// Renders the shape.
        /// </summary>
        /// <param name="renderer">Renderer which renders the shape.</param>
        public override void Render(Renderer renderer)
        {
            bool shadowed = renderer.Shadowed;
            Color color = GetShapeColor();
            if (AlphaTextureCore != null)
            {
                SetAlphaTextureRenderMode();

                gl.BindTexture(TextureTarget.Texture2D, AlphaTextureCore.Name);
                gl.CheckError();
                gl.BindBuffer(BufferTarget.ArrayBuffer, GetTextureBufferName());
                gl.CheckError();
                gl.TexCoordPointer(2, glValueType.Float, 0, (float[])null);
                gl.CheckError();

                //note: SetTextureRenderMode is not do be done with alpha textures!
                //SetTextureRenderMode(AlphaTextureCore);


                gl.ActiveTexture(TextureUnit.Texture1);
                gl.ClientActiveTexture(TextureUnit.Texture1);
                //gl.BlendFunc(BlendFactor.SrcAlpha, BlendFactor.OneMinusSrcAlpha);
                //gl.BlendFunc(BlendFactor.SrcAlpha, BlendFactor.DstAlpha);
                gl.Enable(Capability.Texture2D);
                gl.EnableClientState(ClientStateArray.TextureCoord);
            }

            if (TextureCore != null)
            {
                gl.BindTexture(TextureTarget.Texture2D, TextureCore.Name);
                gl.CheckError();
                gl.BindBuffer(BufferTarget.ArrayBuffer, GetTextureBufferName());
                gl.CheckError();
                gl.TexCoordPointer(2, glValueType.Float, 0, (float[])null);
                gl.CheckError();
            }
            else
            {
                gl.DisableClientState(ClientStateArray.TextureCoord);
                gl.Disable(Capability.Texture2D);
            }

            if (colorArray != null && !shadowed)
            {
                int a = color.A * renderer.Alpha * Opacity >> 16;
                if (this.alpha != a) CreateColorArray(a);
                gl.EnableClientState(ClientStateArray.Color);
                gl.CheckError();
                if (EnableGlesBuffer)
                {
                    //gl.BindBuffer(BufferTarget.ArrayBuffer, glesBuffers[ColorIndex]);
                    //gl.ColorPointer(4, glValueType.UnsignedByte, 0, (int[])null);
                    gl.BindBuffer(BufferTarget.ArrayBuffer, 0);
                    gl.ColorPointer(4, glValueType.UnsignedByte, 0, colorArray);
                }
                else
                {
                    gl.ColorPointer(4, glValueType.UnsignedByte, 0, colorArray);
                }

                gl.CheckError();
                gl.Color4x(65536, 65536, 65536, a << 8);

            }
            else
            {
                int a = color.A * renderer.Alpha * Opacity >> 8;
                if (shadowed)
                {
                    a = a * 2 / 5;
                    //a = a >> 2;                   
                    gl.Color4x(0, 0, 0, a);
                }
                else
                {

                    Color c = color;// ColorCore;
                    gl.Color4x(c.R << 8, c.G << 8, c.B << 8, a);
                }
            }

            if (EnableGlesBuffer)
            {
                gl.BindBuffer(BufferTarget.ArrayBuffer, glesBuffers[VertexIndex]);
                gl.VertexPointer(2, glValueType.Short, 0, (short[])null);
            }
            else
            {
                gl.VertexPointer(2, glValueType.Short, 0, vertexArray);
            }

            gl.CheckError();

            SetTextureRenderMode(TextureCore);


            int indexLength = GetIndexesLength();
            if (indexLength > 0)
            {
                gl.BindBuffer(BufferTarget.ElementArrayBuffer, GetIndexeBufferName());
                gl.DrawElements(PrimitiveType.Triangles, indexLength, glValueType.UnsignedShort, (short[])null);
                gl.CheckError();
            }
            else
            {
                gl.DrawArrays(PrimitiveType.TriangleStrip, 0, textureArraySimple.Length / 2);
            }

            if (TextureCore == null)
            {
                gl.EnableClientState(ClientStateArray.TextureCoord);
                gl.Enable(Capability.Texture2D);
            }

            gl.BindBuffer(BufferTarget.ArrayBuffer, 0);
            gl.BindBuffer(BufferTarget.ElementArrayBuffer, 0);

            gl.TexEnvi(TextureTarget.TextureEnv, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE);
            gl.CheckError();

            if (AlphaTextureCore != null)
            {
                //                gl.BlendFunc(BlendFactor.SrcAlpha, BlendFactor.OneMinusSrcAlpha);
                gl.Disable(Capability.Texture2D);
                gl.DisableClientState(ClientStateArray.TextureCoord);
                gl.ActiveTexture(TextureUnit.Texture0);
                gl.ClientActiveTexture(TextureUnit.Texture0);
                gl.TexEnvi(TextureTarget.TextureEnv, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE);
            }

        }

        protected virtual void SetAlphaTextureRenderMode()
        {
        }

        protected virtual void SetTextureRenderMode(Texture texture)
        {
            if (texture != null && texture.IsSigned)
            {
                gl.TexEnvi(TextureTarget.TextureEnv, gl.GL_TEXTURE_ENV_MODE, gl.GL_COMBINE);
                gl.CheckError();
                gl.TexEnvi(TextureTarget.TextureEnv, gl.GL_COMBINE_RGB, gl.GL_ADD_SIGNED);
                gl.CheckError();
                gl.TexEnvi(TextureTarget.TextureEnv, gl.GL_COMBINE_ALPHA, gl.GL_MODULATE);
                gl.CheckError();
            }
        }

        protected uint GetTextureBufferName()
        {
            switch (StyleCore)
            {
                case RenderStyle.Simple: return staticBuffers[4];
                case RenderStyle.FlatChrome:
                case RenderStyle.Metalic: return staticBuffers[1];
                default: return staticBuffers[0];
            }
        }

        protected int GetIndexesLength()
        {
            switch (StyleCore)
            {
                case RenderStyle.Simple: return 0;
                //case RenderStyle.Simple: return simpleIndexes.Length;
                case RenderStyle.FlatChrome:
                case RenderStyle.Metalic: return chromeIndexes.Length;
                default: return plasticIndexes.Length;
            }
        }

        protected short[] GetIndexPointer()
        {
            switch (StyleCore)
            {
                case RenderStyle.Simple: return null;

                case RenderStyle.FlatChrome:
                case RenderStyle.Metalic: return chromeIndexes;
                default: return plasticIndexes;
            }
        }

        protected uint GetIndexeBufferName()
        {
            switch (StyleCore)
            {
                case RenderStyle.Simple: return 0;
                case RenderStyle.FlatChrome:
                case RenderStyle.Metalic: return staticBuffers[3];
                default: return staticBuffers[2];
            }
        }


        #endregion
    }

}

